// guarded_suspension.cpp


#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <ctime>

#include <chrono>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <queue>
#include <string>
#include <thread>


namespace blogs
{


using std::chrono::milliseconds;
using std::condition_variable;
using std::lock_guard;
using std::mutex;
using std::queue;
using std::shared_ptr;
using std::string;
using std::unique_lock;


class Request
{

  private:

    string
    __name__;


  public:

    Request(string name)
        : __name__(name)
    {
    }


    string
    name() const
    {
        return __name__;
    }


    string
    to_string() const
    {
        return "[ Request " + __name__ + " ]";
    }

};


class Request_Queue
{

  private:

    queue<shared_ptr<Request>>
    __requests__;


    mutex
    __mtx__;


    condition_variable
    __cv__;


  public:

    shared_ptr<Request>
    get_request()
    {
        unique_lock<mutex> lk(__mtx__);
        __cv__.wait(lk,
                    [this] {
                        bool empty = __requests__.empty();
                        if (empty)
                            std::printf("No requests in queue. Waiting...\n");
                        return !empty;
                    });
        shared_ptr<Request> req = __requests__.front();
        __requests__.pop();
        return req;
    }


    void
    put_request(shared_ptr<Request> req)
    {
        lock_guard<mutex> lk(__mtx__);
        __requests__.push(req);
        __cv__.notify_all();
    }

};


class Client
{

  private:

    string
    __name__;


    Request_Queue &
    __queue__;


  public:

    Client(string nm, Request_Queue & queue)
        : __name__(nm), __queue__(queue)
    {
    }


    void
    run()
    {
        for (int i = 0; i < 10000; ++i) {
            shared_ptr<Request> req(new Request(("#" + std::to_string(i))));
            std::printf("%s requests:\t%s\n", __name__.c_str(), req->to_string().c_str());
            __queue__.put_request(req);
            std::this_thread::sleep_for(milliseconds(std::rand()%1000));
        }
    }

};


class Server
{

  private:

    string
    __name__;


    Request_Queue &
    __queue__;


  public:

    Server(string nm, Request_Queue & queue)
        : __name__(nm), __queue__(queue)
    {
    }


    void
    run()
    {
        for (int i = 0; i < 10000; ++i) {
            shared_ptr<Request> req = __queue__.get_request();
            std::printf("%s handles:\t%s\n", __name__.c_str(), req->to_string().c_str());
            std::this_thread::sleep_for(milliseconds(std::rand()%333));
        }
    }

};


void
bye(int sig = SIGTERM)
{
    std::printf("\nBye Guarded Suspension...\n\n");
    std::exit(sig);
}


} // end of namespace blogs


int
main(int argc, char * argv[])
{
    using std::thread;
    using blogs::Request;
    using blogs::Client;
    using blogs::Server;
    using blogs::Request_Queue;

    std::signal(SIGINT, blogs::bye);
    std::srand(std::time(0));

    Request_Queue rq;
    Client c1("Alice", rq);
    Server s1("Bobby", rq);

    thread ct1(&Client::run, &c1);
    thread st1(&Server::run, &s1);

    st1.join();
    ct1.join();

    blogs::bye();
    return 0;
}

